Note: This example does not yield the exact same results as https://niivue.com/demos/features/denoise.html. This notebook does, however, show how one can change the volume img data and save it.¶
In [1]:
from pathlib import Path
from ipyniivue import download_dataset
BASE_API_URL = "https://niivue.com/demos/images/"
DATA_FOLDER = Path("images")
# Download data for example
download_dataset(
BASE_API_URL,
DATA_FOLDER,
files=[
"otsu.nii.gz",
],
)
otsu.nii.gz already exists. Dataset downloaded successfully to images.
In [2]:
try:
from scipy.ndimage import grey_dilation, median_filter
except ModuleNotFoundError:
!pip install scipy
from scipy.ndimage import grey_dilation, median_filter
import ipywidgets as widgets
import numpy as np
from ipyniivue import NiiVue, Volume
from ipyniivue.constants import DragMode, ShowRender
from ipyniivue.utils import find_otsu
# Setup niivue object
nv = NiiVue(
back_color=(0.9, 0.9, 1, 1),
show_3d_crosshair=True,
)
nv.load_volumes([Volume(path=DATA_FOLDER / "otsu.nii.gz")])
imgRaw = None
@nv.on_image_loaded
def on_image_loaded(volume):
"""Handle image loaded."""
global imgRaw
imgRaw = volume.img.copy()
print("Image loaded. Original image data stored.")
@nv.on_location_change
def handle_intensity_change(location):
"""Handle location change."""
with intensity_output:
intensity_output.clear_output()
print(location["string"])
nv.opts.multiplanar_show_render = ShowRender.ALWAYS
nv.opts.yoke_3d_to_2d_zoom = True
nv.set_interpolation(True)
nv.set_clip_plane(0.2, 0, 120)
# Create UI elements
denoise_button = widgets.Button(description="Denoise")
save_button = widgets.Button(description="Save")
cancel_button = widgets.Button(description="Cancel")
apply_button = widgets.Button(description="Apply")
otsu_select = widgets.Dropdown(
options=[
("Very Heavy 3:4", 1),
("Heavy 2:3", 2),
("Medium 1:2", 3),
("Light 1:3", 4),
("Very Light 1:4", 5),
("None", 6),
],
value=5,
description="Reduce dark noise:",
)
dilate_check = widgets.Checkbox(value=True, description="Dilate dark")
denoise_check = widgets.Checkbox(value=False, description="Denoise")
dark_check = widgets.Checkbox(value=True, description="Clip Dark")
intensity_output = widgets.Output()
# Functions to handle UI events
def on_dark_check_change(change):
"""Handle dark check change."""
nv.opts.is_alpha_clip_dark = change["new"]
on_dark_check_change({"new": dark_check.value})
def on_drag_mode_change(change):
"""Handle drag mode change."""
if change.new == "none":
nv.opts.drag_mode = DragMode.NONE
elif change.new == "contrast":
nv.opts.drag_mode = DragMode.CONTRAST
elif change.new == "measurement":
nv.opts.drag_mode = DragMode.MEASUREMENT
elif change.new == "pan":
nv.opts.drag_mode = DragMode.PAN
drag_mode_dropdown = widgets.Dropdown(
options=[
("Drag pan/zoom", "pan"),
("Drag contrast", "contrast"),
("Drag measurement", "measurement"),
("Drag none", "none"),
],
value="pan",
description="Drag Mode:",
)
drag_mode_dropdown.observe(on_drag_mode_change, names="value")
def on_denoise_button_clicked(b):
"""Denoise."""
if denoise_options.layout.display == "none":
denoise_options.layout.display = ""
else:
denoise_options.layout.display = "none"
denoise_button.on_click(on_denoise_button_clicked)
def on_cancel_clicked(b):
"""Cancel/reset operation."""
global imgRaw
nv.volumes[0].img = imgRaw.copy()
def on_save_clicked(b):
"""Save image."""
nv.volumes[0].save_to_disk("denoised.nii")
# Process img functions
def denoise_image(img_raw, nx, ny, nz):
"""Denoise image."""
img_raw = img_raw.reshape(nz, ny, nx)
img_smoothed = median_filter(img_raw, size=3)
return img_smoothed.flatten()
def dilate_image(img_raw, nx, ny, nz):
"""Dilate image."""
img_raw = img_raw.reshape(nz, ny, nx)
img_dilated = grey_dilation(img_raw, size=(3, 3, 3))
return img_dilated.flatten()
def process_volume(change=None):
"""Process volume."""
global imgRaw
if imgRaw is None:
print("Image not loaded yet.")
return
level = otsu_select.value
# Reload original image
img_raw = imgRaw.copy()
nx = int(nv.volumes[0].dims[1])
ny = int(nv.volumes[0].dims[2])
nz = int(nv.volumes[0].dims[3])
if level in [5, 1]:
otsu = 4
elif level in [4, 2]:
otsu = 3
else:
otsu = 2
thresholds = find_otsu(nv.volumes[0], mlevel=otsu)
if len(thresholds) < 3:
print("Threshold calculation failed.")
return
threshold = thresholds[0]
if level == 1:
threshold = thresholds[2]
elif level == 2:
threshold = thresholds[1]
mn = np.min(img_raw)
if level > 5:
threshold = mn
if denoise_check.value:
img_raw = denoise_image(img_raw, nx, ny, nz)
if dilate_check.value and level < 6:
imgMx = dilate_image(img_raw, nx, ny, nz)
else:
imgMx = img_raw.copy()
# Apply threshold
mask = imgMx < threshold
img_processed = img_raw.copy()
img_processed[mask] = mn
img_processed = img_processed.astype(imgRaw.dtype)
nv.volumes[0].img = img_processed
# Attach handlers
apply_button.on_click(process_volume)
cancel_button.on_click(on_cancel_clicked)
save_button.on_click(on_save_clicked)
dark_check.observe(on_dark_check_change, names="value")
otsu_select.observe(process_volume, names="value")
dilate_check.observe(process_volume, names="value")
denoise_check.observe(process_volume, names="value")
# Display all
denoise_options = widgets.VBox(
[
otsu_select,
dilate_check,
denoise_check,
apply_button,
]
)
denoise_options.layout.display = "none"
ui_controls = widgets.VBox(
[
widgets.HBox([denoise_button, save_button, cancel_button]),
denoise_options,
dark_check,
drag_mode_dropdown,
]
)
display(ui_controls)
display(nv)
display(intensity_output)